<?php
/**
 *   Author: Yanlongli <jobs@yanlongli.com>
 *   Date:   2020/4/13
 *   IDE:    PhpStorm
 *   Desc:  ip子网合并 类型 1 out.txt
 */

class Type1
{
    /**
     * @var array[]
     */
    public array $IPs = [];
    /**
     * @var array 丢弃的数据
     */
    public array $discardData = [];

    /**
     * @var string 文件路径
     */
    public string $filePath;

    /**
     * @param string $file 文件地址
     */
    public function run($file)
    {
        // 初始化
        $this->filePath = $file;
        logger("初始化", $file);

        logger("开启文件资源");
        // 打开文件资源
        $fileResource = fopen($file, "r");
        if (!$fileResource) {
            logger("文件打开失败！");
            die();
        }
        $i = 0;
        while ($line = trim(fgets($fileResource))) {
            logger(sprintf("已读取第 %d 行", ++$i));
            /**
             * @var string $ips ip段开始地址
             * @var string $ipe ip段结束地址
             * @var string $regionAbb 国家或地区简写
             * @var string $mask 子网掩码
             * @var string ... 其它无关紧要的参数
             */
            list($ips, $ipe, $regionAbb, $mask) = explode('|', $line);
            $this->mergeIpSection($ips, $mask, $line);
        }
        // 关闭文件资源
        $closeFile = fclose($fileResource);
        logger("数据处理完毕,关闭文件资源", $closeFile ? '关闭成功' : '关闭失败');

        $this->generateNewFiles();
    }

    /**
     * 检测ip段冲突
     * @param int $ip ip段内的任意ip的
     * @param int $mask
     * @param $source
     * @return void
     */
    public function mergeIpSection($ip, $mask, $source)
    {
        logger("开始检测是否存在冲突 ip 段");
        $ip = ip2long($ip);
        foreach ($this->IPs as $key => $ipInfo) {
            $_ip = $ipInfo['ipsection'];
            $_mask = $ipInfo['mask'];
            $maskMin = 32 - min($_mask, $mask);

            // 检测冲突
            if (($_ip >> $maskMin) === ($ip >> $maskMin)) {
                logger("存在冲突 IP 段");
                // 表示冲突
                $type = $_mask <=> $mask;
                if ($type === 1) {
                    logger(sprintf("新 IP 段 %s/%d 段包含旧IP段 %s/%d ，执行替换操作", long2ip($ip), $mask, long2ip($_ip), $_mask));
                    // 保存丢弃的数据
                    $this->discardData[] = $ipInfo['source'];
                    // 删除旧资源
                    unset($this->IPs[$key]);
                    // 新增新资源
                    $this->IPs[] = [
                        'source' => $source,
                        'ipsection' => ip2long($ip),
                        'mask' => $mask
                    ];
                } else {
                    logger(sprintf("旧 IP 段 %s/%d 段包含新IP段 %s/%d ，保持不变", long2ip($ip), $mask, long2ip($_ip), $_mask));
                    // 保存丢弃的数据
                    $this->discardData[] = $source;
                }
                // 终止继续执行
                return;
            }
        }
        logger("无冲突 IP 段");
        logger(sprintf("新 IP 段 %s/%d 已加入", long2ip($ip), $mask));
        // 新增
        $this->IPs[] = [
            'source' => $source,
            'ipsection' => ip2long($ip),
            'mask' => $mask
        ];
        return;
    }

    public function generateNewFiles()
    {
        logger("开始输出数据到文件");
        logger("输出新规则", $this->filePath . '.new');
        file_put_contents($this->filePath . '.new', implode("\n", array_map(fn($item) => $item['source'], $this->IPs)));
        logger("输出废弃规则", $this->filePath . '.dis');
        file_put_contents($this->filePath . '.dis', implode("\n", $this->discardData));
    }
}

function logger(...$arg)
{
    echo date("Y-m-d H:i:s:") . (microtime(true) - time()) . '  |  ' . implode('  |  ', $arg) . PHP_EOL;
}

$startTime = microtime(true);
logger("开始执行");
$run = new Type1();
$run->run("./data/out.txt");
logger("规则集处理完毕");
$endTime = microtime(true);
logger(sprintf("本次共执行 %f 秒", $endTime - $startTime));


